import { sys } from "cc";
import Util from "../utils/Util";
import { WanderNet } from "./Defines";
import Gui from "./GuiMgr";
import Singleton from "./Singleton";

const Key_StorageGameToken = 'Key_StorageGameToken';

function buildQuery(data: any): string {
    let str = '';
    for (let k in data) {
        if (str != '') {
            str += '&';
        }
        str += k + '=' + data[k];
    }
    return encodeURI(str);
}

class Request {
    private _uid = 0;
    private _path!: string;
    private _param: any;
    private _method!: string;
    private _retryfunc: null | ((code: number, cancelFunc: () => void, confirmFunc: () => void) => void) = null;
    private _responseCallback: null | ((data: WanderNet.RecvDataEntity) => void) = null;
    private _timeout!: number;//毫秒
    private _autoretry!: number;
    private _isResponse: boolean = false;
    private _isRequesting: boolean = false;
    private _needauth: boolean = true;
    private _needmask: boolean = true;
    private _deltaTime = 0;

    private _entity!: WanderNet.RecvDataEntity;

    constructor(uid: number, path: string, param?: any, post?: WanderNet.IPostData) {
        this._uid = uid;
        this.reset(path, param, post);
    }

    public get uid(): number { return this._uid; }
    public get deltaTime(): number { return this._deltaTime; }

    public reset(path: string, param?: any, post?: WanderNet.IPostData) {
        this._path = path;
        this._param = param;
        post = post || {};

        // 初始化参数
        this._method = post._method || 'POST';
        this._timeout = post._timeout || 3000;
        this._autoretry = post._autoretry || 0;
        if (post.hasOwnProperty('_needauth')) {
            this._needauth = post._needauth!;
        }
        if (post.hasOwnProperty('_needmask')) {
            this._needmask = post._needmask!;
        }

        // 保留向服务器发送的参数
        for (let k in param) {
            post[k] = param[k];
        }

        if (this._entity) {
            this._entity.reset(post);
        } else {
            this._entity = new WanderNet.RecvDataEntity(post);
        }

        this._retryfunc = post._retryfunc || null;

        // 重置数据
        this._responseCallback = null;
        this._isResponse = false;
        this._isRequesting = false;
    }

    public setResponseCallback(callback: (data: WanderNet.RecvDataEntity) => void) {
        this._responseCallback = callback;
    }

    public start() {
        if (this._isRequesting) return;
        this._needmask && Gui.setRequestMask(true);

        this._isRequesting = true;
        this._autoretry--;

        let xhr = new XMLHttpRequest(),
            isTimeout = false, timeout = this._timeout ? this._timeout : 3000,
            path = this._path, param = this._param, method = this._method;

        // 超时
        let timer = setTimeout(() => {
            isTimeout = true;
            xhr.abort();
            this._setResponse(WanderNet.InnerCode.TIMEOUT, '请求超时');
        }, timeout);

        xhr.onreadystatechange = () => {
            if (xhr.readyState != 4) return;

            if (xhr.status == 200) {
                if (isTimeout) return;
                clearTimeout(timer);
                let res = null;
                try {
                    res = JSON.parse(xhr.responseText);
                    if (!res.hasOwnProperty('code')) {
                        throw new Error('数据格式错误');
                    }
                    if (res.hasOwnProperty('time')) {
                        this._deltaTime = new Date().getTime() - res.time;
                    }
                } catch (err) {
                    res = null;
                    this._setResponse(WanderNet.InnerCode.PARSE_JSON_ERR, 'JSON解析错误');
                }
                if (res) {
                    this._setResponse(res.code, res.msg || "", res.data);
                }
            }
            else {
                this._setResponse(WanderNet.InnerCode.STATUS_CODE_ERR, `状态码错误:${xhr.status}`);
            }
        };
        // 4xx属于应用级别不触发
        xhr.onerror = (event: any) => {
            this._setResponse(WanderNet.InnerCode.STATUS_CODE_ERR, '状态码错误:err');
        };

        let url = path;
        if (method === 'GET') {
            let queryStr = buildQuery(param);
            if (queryStr) {
                url += `?${queryStr}`;
            }
            xhr.open('GET', url, true);
            this._needauth && xhr.setRequestHeader('Authorization', Http.getGameToken());
            xhr.send();
        }
        else if (method === 'POST') {
            xhr.open('POST', url, true);
            this._needauth && xhr.setRequestHeader('Authorization', Http.getGameToken());
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.send(buildQuery(param))
        }
    }

    ////
    private _retry() {
        if (this._isRequesting) return;
        this._isResponse = false;
        this.start();
    }

    private _setResponse(code: number, msg: string, data?: any) {
        if (this._isResponse) return;

        this._isResponse = true;
        this._isRequesting = false;

        let success = code == WanderNet.InnerCode.OK;

        // 自动重试
        if (!success && this._autoretry >= 0) {
            this._retry();
            return;
        }

        // 删除遮罩
        this._needmask && Gui.setRequestMask(false);

        let res = (code: number, msg: string, data?: any) => {
            // 响应回调
            this._entity.msg = msg;
            this._entity.code = code;
            this._entity.data = data;
            if (this._responseCallback) this._responseCallback(this._entity);
        }

        // 重试
        if (!success && this._retryfunc) {
            this._retryfunc(code, () => {
                res(code, msg, data);
            }, () => {
                this._retry();
            });
            return;
        }
        if (!success) Gui.toast(msg);

        res(code, msg, data);
    }
}

type TDriverFunction = (data: any, post: WanderNet.IPostData) => void;

class HttpMgr extends Singleton {

    public get sec(): number {
        return (Math.round((new Date().getTime() - this._deltaTime) / 1000));
    }

    public get msec(): number {
        return new Date().getTime() - this._deltaTime;
    }

    public init(httpUrl: string, driverCb: TDriverFunction) {
        this._url = httpUrl;
        this._driverCb = driverCb;
    }

    public setGameToken(token: string) {
        this._gameToken = token;
        sys.localStorage.setItem(Key_StorageGameToken, token);
    }

    public getGameToken(): string {
        if (this._gameToken) return this._gameToken;
        return sys.localStorage.getItem(Key_StorageGameToken) || '';
    }

    /**
     * 请求
     * @param path 请求路径
     * @param param 上传服务器的参数
     * @param post 客户端参数
     * @returns 返回一个 Promise，并且此 Promise 只会 resolve。所以不需要 try-catch
     */
    public req(path: string, param?: any, post?: WanderNet.IPostData): Promise<WanderNet.RecvDataEntity> {
        return new Promise((res, rej) => {
            let request = this.getRequest(path, param, post);
            let startStampMS = (new Date).getTime();

            let method = 'POST';
            if (post && post._method) method = post._method;

            request.setResponseCallback(data => {
                let duration = (new Date).getTime() - startStampMS;

                this._deltaTime = request.deltaTime;

                let fmtStr = Util.fmtDate('YY-mm-dd HH:MM:SS', Math.floor(startStampMS / 1000)) + `.${startStampMS % 1000}`;
                console.group(`http /${path}`);
                console.log("%c%s(%s)", "color:green", fmtStr, duration / 1000);
                console.log('url:', this._url + "/" + path + (method === 'POST' ? '' : `?${buildQuery(param)}`));
                console.log('param:', param);
                console.log('client:', post);
                console.log('response: ', data.data);
                console.groupEnd();

                // 成功返回，且注册回调
                if (data.ok && data.post && data.post._cbname) {
                    this._driverCb(data.data, data.post);
                }

                res(data);
                this._requestCache.push(request);
            });
            request.start();
        });
    }
    ////
    private getRequest(path: string, param?: any, post?: WanderNet.IPostData): Request {
        if (this._requestCache.length > 0) {
            let request = this._requestCache.shift()!;
            request.reset(this._url + '/' + path, param, post);
            return request;
        }
        return new Request(++this._rid, this._url + '/' + path, param, post);
    }
    //private
    private _rid = 0;
    private _url: string = "";
    private _driverCb: TDriverFunction = null!;
    private _gameToken: string = "";
    private _requestCache: Request[] = [];
    private _deltaTime = 0;
}
const Http = HttpMgr.getInstance() as HttpMgr
export default Http;